<template>
  <div class="cones-visualizer-container">
    <div class="visualizer-card">
      <div class="visualizer-header">
        <div class="header-title">
          <span class="title-text">桩桶可视化</span>
          <div class="statistics">
            <div class="stat passed">
              <div class="stat-value">{{ passedCount }}</div>
              <div class="stat-label">已通过</div>
            </div>
            <div class="stat remaining">
              <div class="stat-value">{{ 20 - passedCount }}</div>
              <div class="stat-label">未通过</div>
            </div>
            <div class="stat online">
              <div class="stat-value">{{ onlineCount }}</div>
              <div class="stat-label">在线</div>
            </div>
            <div class="stat offline">
              <div class="stat-value">{{ 20 - onlineCount }}</div>
              <div class="stat-label">离线</div>
            </div>
          </div>
        </div>
        <div class="header-controls">
          <button class="header-control-btn reset-btn" title="重置桩桶状态" @click="resetCones">
            <el-icon><refresh /></el-icon>
          </button>
          <button 
            class="header-control-btn edit-btn" 
            :class="{ 'active': isEditMode }" 
            title="切换编辑模式"
            @click="toggleEditMode"
          >
            <el-icon><edit /></el-icon>
          </button>
        </div>
      </div>
      
      <div class="visualizer-content" ref="visualizerContentRef">
        <div class="track-container">
          <div class="track-path"></div>
          <div class="track-start-label">起点</div>
          <div class="track-end-label">终点</div>
          
          <!-- 数字孪生轨迹 -->
          <svg class="skater-track-svg">
            <defs>
              <linearGradient id="skaterPathGradient" x1="0%" y1="0%" x2="100%" y2="0%">
                <stop offset="0%" style="stop-color:#00f2fe;stop-opacity:1" />
                <stop offset="100%" style="stop-color:#4facfe;stop-opacity:1" />
              </linearGradient>
              <filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
                <feGaussianBlur stdDeviation="6" result="coloredBlur"/>
                <feMerge>
                    <feMergeNode in="coloredBlur"/>
                    <feMergeNode in="SourceGraphic"/>
                </feMerge>
              </filter>
            </defs>
            <path
              v-if="isPathAnimating"
              ref="skaterPathRef"
              :d="skaterPath"
              class="skater-path"
            />
          </svg>

          <!-- 桩桶循环渲染 -->
          <div 
            v-for="(cone, index) in cones" 
            :key="index"
            class="cone-item" 
            :class="{
              'passed': cone.status === 'passed',
              'missed': cone.status === 'missed',
              'kicked': cone.status === 'kicked',
              'no-receive': cone.status === 'no-receive',
              'offline': !cone.online,
              'flow-light': flowLightActive && (index === flowLightIndex)
            }"
            :style="getConePosition(index)"
            @click="handleConeClick(cone)"
          >
            <div class="cone-body"></div>
            <div class="cone-effect"></div>
            <div class="cone-number">{{ index + 1 }}</div>
          </div>
        </div>
      </div>
      
      <!-- 添加偏移曲线可视化 -->
      <div class="offset-curve-section">
        <h3 class="section-title">过桩轨迹分析</h3>
        <OffsetCurveVisualizer
          :width="1000"
          :height="200"
          :offset-data="offsetData"
        />
      </div>
      
      <div class="visualizer-footer">
        <div class="legend">
          <div class="legend-item">
            <div class="legend-marker passed"></div>
            <span class="legend-text">已通过</span>
          </div>
          <div class="legend-item">
            <div class="legend-marker missed"></div>
            <span class="legend-text">漏桩</span>
          </div>
          <div class="legend-item">
            <div class="legend-marker kicked"></div>
            <span class="legend-text">踢桩</span>
          </div>
          <div class="legend-item">
            <div class="legend-marker no-receive"></div>
            <span class="legend-text">未检测</span>
          </div>
          <div class="legend-item">
            <div class="legend-marker online"></div>
            <span class="legend-text">在线</span>
          </div>
          <div class="legend-item">
            <div class="legend-marker offline"></div>
            <span class="legend-text">离线</span>
          </div>
          <div class="legend-item" v-if="flowLightActive">
            <div class="legend-marker flow-light"></div>
            <span class="legend-text">流水灯</span>
          </div>
        </div>
        <div class="flow-light-indicator" v-if="flowLightActive">
          <el-tag type="warning" effect="light">流水灯效果进行中</el-tag>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, watch, onMounted, onUnmounted, defineExpose, defineEmits, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import OffsetCurveVisualizer from './OffsetCurveVisualizer.vue'

// 常量定义
const CONE_STATUS = {
  NULL: null,
  PASSED: 'passed',   // 状态码 1
  MISSED: 'missed',   // 状态码 0
  KICKED: 'kicked',   // 状态码 2
  NO_RECEIVE: 'no-receive'  // 状态码 -1
}

// 组件事件
const emit = defineEmits(['status-change'])

// 桩桶数据 - 初始状态改为离线
const cones = ref(Array(20).fill().map((_, index) => ({
  id: index + 1,
  status: null, // null, 'passed', 'missed', 'kicked', 'no-receive'
  online: false, // 默认全部离线
  timestamp: null,
  lastHeartbeat: null, // 添加最后心跳时间记录
  passed: false // 新增passed属性
})))

// 心跳计时器对象
const heartbeatTimers = ref({})

// 编辑模式状态
const isEditMode = ref(false)

// 最后通过的桩桶ID (保留用于内部逻辑)
const lastPassedConeId = ref(null)

// 数字孪生轨迹相关
const skaterPathRef = ref(null)
const isPathAnimating = ref(false)
const visualizerContentRef = ref(null)
const visualizerSize = ref({ width: 0, height: 0 })

// 流水灯相关状态
const flowLightActive = ref(false)
const flowLightIndex = ref(0)
let flowLightTimer = null

// 在 script setup 中添加偏移数据状态
const offsetData = ref({})

// 计算属性：已通过的桩桶数量
const passedCount = computed(() => {
  return cones.value.filter(cone => cone.status === CONE_STATUS.PASSED).length
})

// 计算属性：在线的桩桶数量
const onlineCount = computed(() => {
  return cones.value.filter(cone => cone.online).length
})

// 计算属性：漏桩数量
const missedCount = computed(() => {
  return cones.value.filter(cone => cone.status === CONE_STATUS.MISSED).length
})

// 计算属性：踢桩数量
const kickedCount = computed(() => {
  return cones.value.filter(cone => cone.status === CONE_STATUS.KICKED).length
})

// 计算属性：未检测数量
const noReceiveCount = computed(() => {
  return cones.value.filter(cone => cone.status === CONE_STATUS.NO_RECEIVE).length
})

// 计算数字孪生轨迹的SVG路径
const skaterPath = computed(() => {
  if (cones.value.length === 0 || visualizerSize.value.width === 0) return ''

  const { width, height } = visualizerSize.value;
  // 获取桩桶的视觉半径，以便路径可以绕过它
  const coneElementWidth = 22; // .cone-body width
  const coneRadius = coneElementWidth / 2;
  const horizontalPadding = coneRadius + 5; // 路径距离桩桶中心的水平距离

  const pathPoints = cones.value.map((_, index) => {
    const { x, y } = getConePositionValues(index)
    return { x: x / 100 * width, y: y / 100 * height }
  })

  // S型绕桩路径逻辑
  const startPoint = `M ${pathPoints[0].x - horizontalPadding * 2},${pathPoints[0].y}`
  let pathData = [startPoint]
  const curveHeight = 35; // S形曲线的垂直高度

  for (let i = 0; i < pathPoints.length; i++) {
    const p = pathPoints[i];
    const nextP = pathPoints[i + 1];
    
    // 确定曲线方向
    const direction = i % 2 === 0 ? -1 : 1;

    // 起始点 (当前桩桶侧方)
    const startX = p.x;
    const startY = p.y;
    
    if (i < pathPoints.length -1) {
      // 曲线终点 (下一个桩桶与当前桩桶之间)
      const endX = (p.x + nextP.x) / 2;
      const endY = p.y + curveHeight * direction;
      
      // 控制点
      const cp1X = startX + (endX - startX) * 0.5;
      const cp1Y = startY;
      const cp2X = startX + (endX - startX) * 0.5;
      const cp2Y = endY;

      pathData.push(`C ${cp1X},${cp1Y} ${cp2X},${cp2Y} ${endX},${endY}`);

      // 从中间点返回到下一个桩桶侧方
      const returnCp1X = endX + (nextP.x - endX) * 0.5;
      const returnCp1Y = endY;
      const returnCp2X = endX + (nextP.x - endX) * 0.5;
      const returnCp2Y = nextP.y;

      pathData.push(`C ${returnCp1X},${returnCp1Y} ${returnCp2X},${returnCp2Y} ${nextP.x},${nextP.y}`);
    }
  }
  
  return pathData.join(' ');
})

// 获取桩桶位置
function getConePosition(index) {
  const position = getConePositionValues(index)
  return {
    left: `${position.x}%`,
    top: `${position.y}%`
  }
}

// 计算桩桶位置值
function getConePositionValues(index) {
  // 均匀分布在2.5% - 97.5%范围内，确保桩桶完全可见
  const x = 2.5 + (index * 95 / 19)
  const y = 50 // 所有桩桶y坐标固定在中间位置
  
  return { x, y }
}

// 处理桩桶点击
function handleConeClick(cone) {
  if (!isEditMode.value) return
  
  // 只能修改在线桩桶的状态
  if (!cone.online) {
    ElMessage.error('离线桩桶无法修改状态')
    return
  }
  
  // 获取桩桶索引
  const index = cones.value.findIndex(c => c.id === cone.id)
  if (index === -1) return
  
  // 根据点击行为区分操作
  const currentStatus = cones.value[index].status
  
  // 简化状态切换: 在通过(CONE_STATUS.PASSED)和漏桩(CONE_STATUS.MISSED)之间切换
  let newStatus
  if (currentStatus === CONE_STATUS.PASSED) {
    newStatus = CONE_STATUS.MISSED
  } else {
    newStatus = CONE_STATUS.PASSED
  }
  
  // 更新状态
  cones.value[index].status = newStatus
  cones.value[index].timestamp = newStatus === CONE_STATUS.PASSED ? Date.now() : null
  
  // 更新最后通过的桩桶
  updateLastPassedCone()
  
  // 发出状态变更事件
  emitStatusChange()
}

// 更新最后通过的桩桶
function updateLastPassedCone() {
  const passedCones = cones.value.filter(cone => cone.status === CONE_STATUS.PASSED)
  if (passedCones.length === 0) {
    lastPassedConeId.value = null
    return
  }
  
  // 找到时间戳最新的桩桶
  const latestCone = passedCones.reduce((latest, cone) => {
    if (!latest.timestamp || cone.timestamp > latest.timestamp) {
      return cone
    }
    return latest
  }, { timestamp: 0 })
  
  lastPassedConeId.value = latestCone.id
}

// 重置桩桶状态
function resetCones() {
  cones.value.forEach(cone => {
    // 清除状态，但保留在线状态
    cone.status = null
    cone.timestamp = null
    
    // 不修改在线状态，保持现状
    // cone.online = false 
  })
  
  lastPassedConeId.value = null
  
  // 重置路径动画
  isPathAnimating.value = false

  // 重置偏移数据
  const resetOffsetData = {}
  for (let i = 1; i <= 20; i++) {
    const idStr = i.toString().padStart(3, '0')
    resetOffsetData[`${idStr}dir`] = 'n'
    resetOffsetData[`${idStr}val`] = '0'
  }
  
  // 发送重置后的偏移数据
  if (mqttClient.value && mqttClient.value.connected) {
    const publishTopic = 'roller/pile/pub/a'
    mqttClient.value.publish(publishTopic, JSON.stringify(resetOffsetData))
  }
  
  // 发出状态变更事件
  emitStatusChange()
  
  console.log('已重置所有桩桶状态和偏移轨迹')
}

// 切换编辑模式
function toggleEditMode() {
  isEditMode.value = !isEditMode.value
  
  ElMessage({
    message: isEditMode.value ? '已启用编辑模式' : '已退出编辑模式',
    type: isEditMode.value ? 'success' : 'info'
  })
}

// 发出状态变化事件给父组件
function emitStatusChange() {
  emit('status-change', {
    passedCount: passedCount.value,
    missedCount: missedCount.value,
    kickedCount: kickedCount.value,
    noReceiveCount: noReceiveCount.value,
    lastPassedConeId: lastPassedConeId.value
  })
}

// 处理MQTT消息更新桩桶状态
function handlePileStateMessage(topic, message) {
  try {
    // 检查是否是流水灯主题
    if (topic === 'roller/pile/pub/a') {
      const data = JSON.parse(message.toString())
      
      // 如果消息包含偏移数据（检查是否有 001dir 字段）
      if (data['001dir'] !== undefined) {
        console.log('收到桩桶偏移数据:', data)
        offsetData.value = data
        return
      }
      
      handleFlowLightMessage(topic, message)
      return
    }
    
    // 解析主题获取桩桶ID
    const topicParts = topic.split('/')
    if (topicParts.length < 4 || !topic.endsWith('/status')) return
    
    const coneIdStr = topicParts[2]
    const coneId = parseInt(coneIdStr, 10)
    
    if (isNaN(coneId) || coneId < 1 || coneId > 20) return
    
    console.log(`收到桩桶 ${coneId} 心跳消息: ${topic}`)
    
    // 更新桩桶状态
    const index = cones.value.findIndex(cone => cone.id === coneId)
    if (index !== -1) {
      // 更新桩桶在线状态
      const wasOffline = !cones.value[index].online
      cones.value[index].online = true
      cones.value[index].lastHeartbeat = Date.now()
      
      if (wasOffline) {
        console.log(`桩桶 ${coneId} 已上线`)
        // 触发状态变更事件，更新统计信息
        emitStatusChange()
      }
      
      // 设置或重置心跳超时计时器
      if (heartbeatTimers.value[coneId]) {
        clearTimeout(heartbeatTimers.value[coneId])
      }
      
      // 如果流水灯不处于活动状态，才设置心跳超时
      if (!flowLightActive.value) {
        heartbeatTimers.value[coneId] = setTimeout(() => {
          markConeOffline(coneId)
        }, 5000) // 5秒超时
      }
      
      try {
        // 解析消息内容
        const data = JSON.parse(message.toString())
        console.log(`桩桶 ${coneId} 状态数据:`, data)
        
        // 如果消息包含状态信息，则更新状态
        if (data.status !== undefined) {
          // 尝试将状态解析为整数
          const statusCode = parseInt(data.status, 10)
          let newStatus = null
          
          // 根据状态码设置状态
          if (!isNaN(statusCode)) {
            switch (statusCode) {
              case 1: newStatus = CONE_STATUS.PASSED; break;
              case 0: newStatus = CONE_STATUS.MISSED; break;
              case 2: newStatus = CONE_STATUS.KICKED; break;
              case -1: newStatus = CONE_STATUS.NO_RECEIVE; break;
              default: newStatus = null;
            }
            
            console.log(`桩桶 ${coneId} 状态码=${statusCode}, 转换为状态=${newStatus}`)
            
            // 更新桩桶状态
            cones.value[index].status = newStatus
            
            // 同步更新passed属性，确保状态一致性
            cones.value[index].passed = (newStatus === CONE_STATUS.PASSED)
            
            // 如果是通过状态，更新时间戳
            if (newStatus === CONE_STATUS.PASSED) {
              cones.value[index].timestamp = Date.now()
              updateLastPassedCone()
            } else {
              // 清除时间戳以保持一致性
              cones.value[index].timestamp = null
            }
            
            // 发出状态变更事件
            emitStatusChange()
          } else {
            console.warn(`桩桶 ${coneId} 状态码无效: ${data.status}`)
          }
        }
      } catch (e) {
        console.warn(`桩桶 ${coneId} 消息解析失败，仅更新在线状态:`, e)
      }
    }
  } catch (error) {
    console.error('处理桩桶状态消息出错:', error)
  }
}

// 标记桩桶离线
function markConeOffline(coneId) {
  const index = cones.value.findIndex(cone => cone.id === coneId)
  if (index !== -1 && cones.value[index].online) {
    cones.value[index].online = false
    console.log(`桩桶 ${coneId} 已离线（5秒未收到心跳）`)
    // 触发状态变更事件，更新统计信息
    emitStatusChange()
  }
}

// 新增：在父组件为桩桶可视化组件设置MQTT客户端
function setMqttClient(client) {
  if (!client) {
    console.warn('传入的MQTT客户端为空')
    return
  }
  
  console.log('为桩桶可视化组件设置MQTT客户端')
  
  // 先移除之前的消息处理器，避免重复处理
  if (client.listenerCount('message') > 0) {
    client.removeListener('message', handlePileStateMessage)
  }
  
  // 添加消息处理器
  client.on('message', handlePileStateMessage)
  console.log('已添加桩桶状态消息处理器')
  
  // 订阅桩桶主题
  subscribeAllConesTopics(client)
}

// 新增：订阅所有桩桶的状态主题
function subscribeAllConesTopics(mqttClient) {
  if (!mqttClient || !mqttClient.connected) {
    console.error('MQTT客户端未连接，无法订阅桩桶主题')
    return
  }
  
  console.log('开始订阅所有桩桶状态主题...')
  
  // 为所有20个桩桶订阅状态主题
  for (let i = 1; i <= 20; i++) {
    const coneIdStr = i.toString().padStart(3, '0') // 格式化为三位数，如：001, 009, 020
    const topic = `roller/pile/${coneIdStr}/status`
    
    console.log(`订阅桩桶 ${i} 状态主题: ${topic}`)
    
    mqttClient.subscribe(topic, (err) => {
      if (err) {
        console.error(`订阅桩桶状态主题失败: ${topic}`, err)
      } else {
        console.log(`成功订阅桩桶状态主题: ${topic}`)
      }
    })
  }
  
  // 订阅流水灯控制主题
  const flowLightTopic = 'roller/pile/pub/a'
  mqttClient.subscribe(flowLightTopic, (err) => {
    if (err) {
      console.error(`订阅流水灯主题失败: ${flowLightTopic}`, err)
    } else {
      console.log(`成功订阅流水灯主题: ${flowLightTopic}`)
    }
  })
  
  console.log('所有桩桶主题订阅完成')
}

// 处理流水灯消息
function handleFlowLightMessage(topic, message) {
  if (topic !== 'roller/pile/pub/a') return
  
  try {
    const data = JSON.parse(message.toString())
    console.log('收到流水灯控制消息:', data)
    
    // 修改：只有在所有桩桶都在线时才响应流水灯开启指令
    if (data.race === 'on') {
      // 检查是否所有桩桶都在线
      if (onlineCount.value < 20) {
        console.log(`当前只有 ${onlineCount.value} 个桩桶在线，无法启动流水灯，需要全部20个桩桶在线`)
        return
      }
      
      // 全部桩桶在线，继续处理流水灯开启逻辑
      // 重置所有桩桶状态
      cones.value.forEach(cone => {
        cone.status = null
        cone.timestamp = null
        cone.online = true // 设置所有桩桶为在线状态
      })
      
      // 重置最后通过的桩桶ID
      lastPassedConeId.value = null
      
      // 启动流水灯效果
      startFlowLight()
      
      // 发出状态变更事件
      emitStatusChange()
      
      console.log('流水灯开始，所有桩桶已设置为在线状态')
    } 
    // 如果是结束流水灯消息
    else if (data.race === 'off') {
      stopFlowLight()
      console.log('流水灯结束')
    } 
    // 如果是结果消息 (检查格式为: { "001": 1, "002": 0, ... })
    else if (Object.keys(data).some(key => key.match(/^\d{3}$/))) {
      handleConesResultMessage(data)
    }
  } catch (error) {
    console.error('处理流水灯消息出错:', error)
  }
}

// 处理桩桶结果消息
function handleConesResultMessage(data) {
  console.log('收到桩桶状态结果消息:', data)
  
  // 遍历所有桩桶ID (001-020)
  for (let i = 1; i <= 20; i++) {
    const coneIdStr = i.toString().padStart(3, '0') // 格式化为三位数
    
    if (data[coneIdStr] !== undefined) {
      const status = parseInt(data[coneIdStr], 10)
      const index = cones.value.findIndex(cone => cone.id === i)
      
      if (index !== -1) {
        // 更新桩桶状态
        let newStatus = null
        
        // 根据状态码设置状态
        switch (status) {
          case 1: newStatus = CONE_STATUS.PASSED; break;
          case 0: newStatus = CONE_STATUS.MISSED; break;
          case 2: newStatus = CONE_STATUS.KICKED; break;
          case -1: newStatus = CONE_STATUS.NO_RECEIVE; break;
          default: newStatus = null;
        }
        
        // 更新桩桶状态
        cones.value[index].status = newStatus
        
        // 同步更新passed属性，确保状态一致性
        cones.value[index].passed = (newStatus === CONE_STATUS.PASSED)
        
        // 如果是通过的桩桶，记录时间戳
        if (newStatus === CONE_STATUS.PASSED) {
          cones.value[index].timestamp = Date.now()
        } else {
          // 清除时间戳以保持一致性
          cones.value[index].timestamp = null
        }
        
        console.log(`桩桶 ${i} 状态已更新为 ${newStatus}`)
      }
    }
  }
  
  // 更新最后通过的桩桶
  updateLastPassedCone()
  
  // 发出状态变更事件
  emitStatusChange()
  
  console.log('所有桩桶状态更新完成')
}

// 启动流水灯效果
function startFlowLight() {
  if (flowLightActive.value) return
  
  flowLightActive.value = true
  flowLightIndex.value = 0
  
  // 启动路径动画
  isPathAnimating.value = true

  // 停止之前可能存在的定时器
  if (flowLightTimer) {
    clearInterval(flowLightTimer)
    flowLightTimer = null
  }

  // 启动流水灯定时器
  flowLightTimer = setInterval(() => {
    // 当前激活的桩桶索引
    flowLightIndex.value = (flowLightIndex.value + 1) % 20
  }, 150)

  // 流水灯开启时确保所有桩桶保持在线状态
  cones.value.forEach(cone => {
    cone.online = true
    
    // 清除该桩桶的离线计时器
    if (heartbeatTimers.value[cone.id]) {
      clearTimeout(heartbeatTimers.value[cone.id])
      heartbeatTimers.value[cone.id] = null
    }
  })
  
  console.log('流水灯效果已启动，所有桩桶已设置为在线状态')
}

// 停止流水灯效果
function stopFlowLight() {
  if (!flowLightActive.value) return
  
  flowLightActive.value = false
  
  if (flowLightTimer) {
    clearInterval(flowLightTimer)
    flowLightTimer = null
  }
  
  // 停止路径动画
  isPathAnimating.value = false

  console.log('流水灯效果已停止')
  
  // 比赛结束后，恢复心跳检测机制
  // 为所有桩桶设置心跳超时计时器
  cones.value.forEach(cone => {
    if (cone.online) {
      // 为在线桩桶设置心跳超时计时器
      if (heartbeatTimers.value[cone.id]) {
        clearTimeout(heartbeatTimers.value[cone.id])
      }
      
      heartbeatTimers.value[cone.id] = setTimeout(() => {
        markConeOffline(cone.id)
      }, 5000) // 5秒超时
    }
  })
}

// 监听状态变化，自动发送状态更新事件
watch([passedCount, missedCount, kickedCount, noReceiveCount], () => {
  emitStatusChange()
})

// 添加监听在线桩桶数量变化的函数
watch(() => onlineCount.value, (newOnlineCount) => {
  // 当所有20个桩桶都在线时，才处理流水灯控制消息
  console.log(`在线桩桶数量变化：${newOnlineCount}`)
  
  if (newOnlineCount === 20) {
    console.log('全部20个桩桶已在线，可以响应流水灯指令')
  } else if (newOnlineCount < 20 && flowLightActive.value) {
    // 如果有桩桶离线且流水灯正在运行，停止流水灯
    stopFlowLight()
    console.log('检测到桩桶离线，已停止流水灯效果')
  }
})

// 组件挂载时初始化
let resizeObserver = null
onMounted(() => {
  if (visualizerContentRef.value) {
    resizeObserver = new ResizeObserver(entries => {
      for (let entry of entries) {
        visualizerSize.value = {
          width: entry.contentRect.width,
          height: entry.contentRect.height
        }
      }
    });

    resizeObserver.observe(visualizerContentRef.value);
  }
  // 发出状态变更事件
  emitStatusChange()
})

// 组件卸载时清理
onUnmounted(() => {
  if (resizeObserver && visualizerContentRef.value) {
    resizeObserver.unobserve(visualizerContentRef.value);
  }
  // 清除所有心跳计时器
  Object.values(heartbeatTimers.value).forEach(timer => {
    if (timer) clearTimeout(timer)
  })
  
  // 清除流水灯定时器
  if (flowLightTimer) {
    clearInterval(flowLightTimer)
    flowLightTimer = null
  }
})

// 暴露给父组件的方法
defineExpose({
  resetCones,
  handlePileStateMessage,
  handleConesResultMessage,
  startFlowLight,
  stopFlowLight,
  subscribeAllConesTopics, // 暴露订阅方法给父组件
  setMqttClient // 添加设置MQTT客户端的方法
})
</script>

<style scoped>
.cones-visualizer-container {
  margin-bottom: 0;
}

.visualizer-card {
  background: white;
  border-radius: 16px;
  padding: 25px 30px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.05);
  position: relative;
  overflow: hidden;
  border: 1px solid rgba(228, 232, 236, 0.8);
  transition: all 0.3s ease;
  border-left: 4px solid #409EFF;
  background: linear-gradient(135deg, #ffffff 0%, #f0f7ff 100%);
}

.visualizer-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 25px;
  position: relative;
  z-index: 1;
}

.header-title {
  display: flex;
  align-items: center;
  gap: 20px;
}

.title-text {
  font-size: 26px;
  color: #303133;
  font-weight: 600;
  position: relative;
  padding-left: 15px;
}

.title-text::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 5px;
  height: 25px;
  background: linear-gradient(135deg, #409EFF 0%, #36acfe 100%);
  border-radius: 3px;
}

.statistics {
  display: flex;
  gap: 15px;
}

.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 8px 15px;
  border-radius: 12px;
  border: 1px solid rgba(0, 0, 0, 0.05);
  transition: all 0.3s;
}

.stat.passed {
  background: rgba(103, 194, 58, 0.1);
}

.stat.remaining {
  background: rgba(230, 162, 60, 0.1);
}

.stat.online {
  background: rgba(64, 158, 255, 0.1);
}

.stat.offline {
  background: rgba(144, 147, 153, 0.1);
}

.stat-value {
  font-size: 20px;
  font-weight: 700;
}

.stat.passed .stat-value {
  color: #67c23a;
}

.stat.remaining .stat-value {
  color: #e6a23c;
}

.stat.online .stat-value {
  color: #409EFF;
}

.stat.offline .stat-value {
  color: #909399;
}

.stat-label {
  font-size: 12px;
  color: #909399;
}

.header-controls {
  display: flex;
  gap: 10px;
}

.header-control-btn {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  cursor: pointer;
  transition: all 0.3s;
  color: white;
  font-size: 16px;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}

.reset-btn {
  background: linear-gradient(135deg, #409EFF 0%, #36acfe 100%);
}

.reset-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 15px rgba(64, 158, 255, 0.3);
}

.edit-btn {
  background: linear-gradient(135deg, #909399 0%, #a6a9ad 100%);
}

.edit-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 15px rgba(144, 147, 153, 0.3);
}

.edit-btn.active {
  background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%) !important;
  box-shadow: 0 6px 15px rgba(103, 194, 58, 0.3);
}

.visualizer-content {
  position: relative;
  height: 180px;
  padding: 20px;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 15px;
  border: 1px dashed #d4e2f9;
  overflow: hidden;
  margin-bottom: 20px;
}

.track-container {
  position: relative;
  width: 100%;
  height: 100%;
}

.track-path {
  position: absolute;
  top: 50%;
  left: 2.5%;
  width: 95%;
  height: 1px;
  background-color: rgba(64, 158, 255, 0.3);
  border: none;
  z-index: 1;
}

.track-start-label, .track-end-label {
  position: absolute;
  top: calc(50% + 30px);
  font-size: 12px;
  color: #606266;
  font-weight: 500;
}

.track-start-label {
  left: 2.5%;
  transform: translateX(0);
}

.track-end-label {
  right: 2.5%;
  transform: translateX(0);
}

.cone-item {
  position: absolute;
  width: 30px;
  height: 40px;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  transition: all 0.3s ease;
  z-index: 2;
  cursor: pointer;
}

.cone-body {
  width: 22px;
  height: 22px;
  background: #67c23a;
  border-radius: 50%; 
  transition: all 0.4s cubic-bezier(0.25, 1, 0.5, 1);
  position: relative;
  box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2);
  border: 1px solid rgba(0, 0, 0, 0.1);
  z-index: 5;
}

.cone-item.passed .cone-body {
  background: linear-gradient(145deg, #4facfe, #00f2fe) !important;
  transform: scale(1.15);
  box-shadow: 0 0 12px rgba(79, 172, 254, 0.7);
  border: 1px solid rgba(79, 172, 254, 0.6);
}

.cone-item.missed .cone-body {
  background: linear-gradient(145deg, #ff6b6b, #f56c6c) !important;
  transform: scale(1.05);
  box-shadow: 0 0 12px rgba(245, 108, 108, 0.7);
  border: 1px solid rgba(245, 108, 108, 0.6);
}

.cone-item.kicked .cone-body {
  background: linear-gradient(145deg, #ffa53e, #e6a23c) !important;
  transform: scale(1.05);
  box-shadow: 0 0 12px rgba(230, 162, 60, 0.7);
  border: 1px solid rgba(230, 162, 60, 0.6);
}

.cone-item.no-receive .cone-body {
  background: linear-gradient(145deg, #a0a3a8, #909399) !important;
  transform: scale(1.05);
  box-shadow: 0 0 12px rgba(144, 147, 153, 0.7);
  border: 1px solid rgba(144, 147, 153, 0.6);
}

.cone-item.no-receive .cone-body::after {
  content: '!';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: white;
  font-weight: bold;
  font-size: 10px;
}

.cone-item.offline .cone-body {
  background: #909399 !important;
  filter: grayscale(0.7) brightness(0.85);
  transform: scale(0.95);
  box-shadow: none;
  border: 1px solid rgba(144, 147, 153, 0.3);
  opacity: 0.7;
}

.cone-item.flow-light .cone-body {
  background: linear-gradient(145deg, #ffbc1f, #ff7e00) !important;
  transform: scale(1.3);
  box-shadow: 0 0 15px rgba(255, 170, 0, 0.6);
  border: 1px solid rgba(255, 126, 0, 0.7);
}

.cone-item .cone-number {
  position: absolute;
  top: -20px;
  font-size: 12px;
  font-weight: bold;
  color: #303133;
  background: rgba(255, 255, 255, 0.9);
  border-radius: 50%;
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  z-index: 8;
  transition: all 0.3s ease;
  border: 1px solid rgba(255, 255, 255, 0.8);
}

.cone-item.passed .cone-number {
  color: #409EFF;
  background: rgba(255, 255, 255, 0.95);
  border-color: #409EFF;
  font-weight: 700;
}

.cone-item.missed .cone-number {
  color: #F56C6C;
  background: rgba(255, 255, 255, 0.95);
  border-color: #F56C6C;
}

.cone-item.kicked .cone-number {
  color: #E6A23C;
  background: rgba(255, 255, 255, 0.95);
  border-color: #E6A23C;
}

.cone-item.no-receive .cone-number {
  color: #909399;
  background: rgba(255, 255, 255, 0.95);
  border-color: #909399;
}

.cone-item.offline .cone-number {
  opacity: 0.7;
  color: #909399;
  background: rgba(240, 240, 240, 0.8);
  border-color: rgba(144, 147, 153, 0.3);
}

.cone-item .cone-effect {
  position: absolute;
  bottom: -4px;
  width: 18px;
  height: 4px;
  background: rgba(0, 0, 0, 0.15);
  border-radius: 50%;
  filter: blur(1px);
  transition: all 0.4s cubic-bezier(0.25, 1, 0.5, 1);
  z-index: 4;
}

.cone-item.passed .cone-effect {
  width: 22px;
  background: rgba(79, 172, 254, 0.25);
  filter: blur(2px);
}

.cone-item.offline .cone-effect {
  opacity: 0.3;
  width: 16px;
}

.visualizer-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 5px;
}

.legend {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
}

.legend-marker {
  width: 15px;
  height: 15px;
  border-radius: 4px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  transition: all 0.3s ease;
}

.legend-text {
  font-size: 14px;
  color: #606266;
  transition: all 0.3s ease;
}

.legend-marker.passed {
  background: linear-gradient(145deg, #4facfe, #00f2fe);
}

.legend-marker.missed {
  background: linear-gradient(145deg, #ff6b6b, #f56c6c);
}

.legend-marker.kicked {
  background: linear-gradient(145deg, #ffa53e, #e6a23c);
}

.legend-marker.no-receive {
  background: linear-gradient(145deg, #a0a3a8, #909399);
  position: relative;
}

.legend-marker.no-receive::after {
  content: '!';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: white;
  font-weight: bold;
  font-size: 10px;
}

.legend-marker.online {
  background: #67c23a;
}

.legend-marker.offline {
  background: #909399;
}

.legend-marker.flow-light {
  background: linear-gradient(145deg, #ffbc1f, #ff7e00);
}

.legend-item:hover .legend-marker {
  transform: scale(1.1);
}

.legend-item:hover .legend-text {
  color: #409EFF;
}

.flow-light-indicator {
  animation: flow-indicator-blink 1s infinite alternate;
}

@keyframes flow-indicator-blink {
  from {
    opacity: 0.7;
  }
  to {
    opacity: 1;
  }
}

@keyframes move-light-spot {
  from {
    stroke-dashoffset: 2500; /* A large number to start off-screen */
  }
  to {
    stroke-dashoffset: 0;
  }
}

/* 数字孪生轨迹样式 */
.skater-track-svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 3; /* 确保在桩桶和路径之间 */
  pointer-events: none; /* 允许点击穿透 */
}

.skater-path {
  fill: none;
  stroke: url(#skaterPathGradient);
  stroke-width: 3.5px;
  stroke-linecap: round;
  filter: url(#glow);
  stroke-dasharray: 50 2500; /* Light spot (50) and a long gap */
  animation: move-light-spot 3s linear infinite;
}

.offset-curve-section {
  margin-top: 20px;
  padding: 20px;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 15px;
  border: 1px dashed #d4e2f9;
}

.section-title {
  font-size: 18px;
  color: #303133;
  margin-bottom: 20px;
  font-weight: 600;
  position: relative;
  padding-left: 15px;
}

.section-title::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 4px;
  height: 18px;
  background: linear-gradient(135deg, #409EFF 0%, #36acfe 100%);
  border-radius: 2px;
}

@media (max-width: 992px) {
  .visualizer-content {
    height: 160px;
  }
  
  .header-title {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
  
  .statistics {
    flex-wrap: wrap;
  }
}

@media (max-width: 768px) {
  .visualizer-card {
    padding: 20px;
  }
  
  .visualizer-content {
    height: 140px;
    padding: 10px;
  }
  
  .visualizer-header {
    flex-direction: column;
    gap: 15px;
    align-items: flex-start;
  }
  
  .header-controls {
    align-self: flex-end;
  }
  
  .visualizer-footer {
    flex-direction: column;
    gap: 10px;
    align-items: flex-start;
  }
}
</style> 